package com.ok.common.utils

import android.app.ActivityManager
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Debug
import android.os.Process
import com.jakewharton.processphoenix.ProcessPhoenix
import com.ok.common.base.AppContext
import com.ok.common.base.BaseApplication
import com.ok.common.mqtt.MqttClient
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.System.exit
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit

class CrashHandler(
    context: Context,
    private val restartActivity: Class<*>? = null,
    private val versionName: String,
    private val versionCode: Int,
    private val applicationStartTime: Long = System.currentTimeMillis(),
    private val maxCrashFileSize: Int = 30,
    private val maxHprofFileSize: Int = 2,
    private val rootPath: String = "app",
    private val crashDir: String = "/crash",
    private val crashHprofDir: String = "/hprof",
    private val handlerCrashCallback: HandlerCrashCallback? = null
) : Thread.UncaughtExceptionHandler {

    companion object {
        private const val TAG = "CrashHandler"
    }

    private val applicationContext: Context = context.applicationContext

    /**
     * 系统默认的UncaughtException处理类
     */
    private val defaultHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler()

    override fun uncaughtException(thread: Thread, ex: Throwable) {
        ex.printStackTrace()
        if (!handleException(thread, ex) && defaultHandler != null) {
            // 如果自定义的没有处理则让系统默认的异常处理器来处理
            defaultHandler.uncaughtException(thread, ex)
        } else {
            try {
                TimeUnit.SECONDS.sleep(1)
            } catch (ignore: InterruptedException) {
            }
            LogUtils.e(TAG, "kill process")
//            Process.killProcess(Process.myPid())
//            val activityM: ActivityManager =
//                applicationContext.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
//            activityM.killBackgroundProcesses(applicationContext.getPackageName())
//            val intent = Intent(applicationContext, restartActivity)
//            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
//            val pendingIntent = PendingIntent.getActivity(
//                applicationContext,
//                0,
//                intent,
//                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
//            )
//            val alarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
//            alarmManager?.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 100L, pendingIntent)
//            exit(0)
            ProcessPhoenix.triggerRebirth(AppContext, Intent(AppContext, restartActivity))
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex 异常信息
     * @return true 如果处理了该异常信息;否则返回false.
     */
    @Synchronized
    private fun handleException(thread: Thread, ex: Throwable): Boolean {
        val crashLog = collectDeviceInfo(thread, ex)
        LogUtils.e(TAG, "crash log:\n$crashLog")
        val crashFile = saveCrashInfo2File(crashLog)
        var dumpFile: File? = null
        if (ex is OutOfMemoryError) {
            dumpFile = saveDumpInfo2File()
        }
        handlerCrashCallback?.invoke(crashFile, dumpFile)
        return true
    }

    /**
     * 收集设备参数信息
     *
     * @param throwable
     */
    private fun collectDeviceInfo(thread: Thread, throwable: Throwable): String {
        val sw = StringWriter()
        throwable.printStackTrace(PrintWriter(sw))
        val stackTrace = sw.buffer.toString()
        return StringBuilder().apply {
            listOf(
                "Vendor" to Build.MANUFACTURER,
                "OSVersion" to "${Build.VERSION.RELEASE}_${Build.VERSION.SDK_INT}",
                "model" to Build.MODEL,
                "cpuAbi" to Build.CPU_ABI,
                "product" to Build.PRODUCT,
                "versionName" to versionName,
                "versionCode" to versionCode,
                "startTime" to applicationStartTime,
                "thread" to thread.name,
                "crashContent" to "\n$stackTrace"
            ).forEach {
                append(it.first).append("=").append(it.second).append("\r\n")
            }
        }.toString()
    }

    private fun saveCrashInfo2File(crashLog: String): File? {
        val fileDir = "$rootPath${crashDir}"
        val date = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date())
        val file = FileUtils.createFile("$date.log", fileDir)
        try {
            val listFile = FileUtils.getFile(fileDir)?.listFiles() ?: return null
            if (listFile.size >= maxCrashFileSize) {
                listFile[0].delete()
                LogUtils.i(TAG, "crash log deleted, log file: " + listFile[0].name)
            }
            FileOutputStream(file).use {
                it.write(crashLog.toByteArray())
            }
            LogUtils.e(TAG, "crash log saved ${file?.path}")
            return file
        } catch (e: Throwable) {
            e.printStackTrace()
        }
        return null
    }

    private fun saveDumpInfo2File(): File? {
        try {
            System.gc()
            TimeUnit.SECONDS.sleep(2)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        val fileDir = "$rootPath${crashHprofDir}"
        val date = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()).format(Date())
        val file = FileUtils.createFile("$date.hprof", fileDir)
        try {
            val listFile = FileUtils.getFile(fileDir)?.listFiles() ?: return null
            if (listFile.size >= maxHprofFileSize) {
                listFile[0].delete()
                LogUtils.i(TAG, "hprof log delete, log file: " + listFile[0].name)
            }
            Debug.dumpHprofData(file?.absolutePath)
            LogUtils.e(TAG, "hprof log saved")
            return file
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }

    fun delete(file: File) {
        FileUtils.deleteFile(file)
    }

}

typealias HandlerCrashCallback = (crashFile: File?, dumpFile: File?) -> Unit
